//bx �� windows �г�ͻ��windows ���ں���
#include "bgfx_imgui.h"
#include <bgfx/embedded_shader.h>
#include "imgui/imgui.h"
#include "innelshader/vs_imgui_image.bin.h";
#include "innelshader/fs_imgui_image.bin.h";
#include "innelshader/vs_imgui_texture.bin.h";
#include "innelshader/fs_imgui_texture.bin.h";
#include "innelshader/vs_ocornut_imgui.bin.h"
#include "innelshader/fs_ocornut_imgui.bin.h"
#include <bx/math.h>

#define IMGUI_FLAGS_NONE UINT8_C(0x00)
#define IMGUI_FLAGS_ALPHA_BLEND UINT8_C(0x01)
static const bgfx::EmbeddedShader s_embeddedShaders[] =
	{
		BGFX_EMBEDDED_SHADER(vs_ocornut_imgui),
		BGFX_EMBEDDED_SHADER(fs_ocornut_imgui),
		BGFX_EMBEDDED_SHADER(vs_imgui_image),
		BGFX_EMBEDDED_SHADER(fs_imgui_image),
		BGFX_EMBEDDED_SHADER(vs_imgui_texture),
		BGFX_EMBEDDED_SHADER(fs_imgui_texture),
		BGFX_EMBEDDED_SHADER_END()};

bgfx::VertexLayout bgfx_imgui::m_layout;
bgfx::TextureHandle bgfx_imgui::m_texture;

bgfx::ProgramHandle bgfx_imgui::m_program;
bgfx::UniformHandle bgfx_imgui::u_imageLodEnabled;

bgfx::ProgramHandle bgfx_imgui::m_imageProgram;
bgfx::UniformHandle bgfx_imgui::s_tex;
std::map<int, int> bgfx_imgui::mapViewID;
void bgfx_OnCreateSubWindow(ImGuiViewport *viewport);
void bgfx_OnDestorySubWindow(ImGuiViewport *viewport);
void bgfx_OnSetWindowSize(ImGuiViewport *viewport, ImVec2 size);
void bgfx_RenderWindow(ImGuiViewport *viewport, void *arg);
void bgfx_SwapBuffers(ImGuiViewport *viewport, void *arg);
struct viewData
{
	bgfx::FrameBufferHandle framebuffer;
	int width;
	int height;
};
//void bgfx_imgui::ClearAllFrameBuffer()
//{
//	for (auto it = mapViewID.begin(); it != mapViewID.end(); ++it)
//	{
//		auto view = ImGui::FindViewportByID(it->first);
//		auto _viewdata = (viewData*)view->RendererUserData;
//		if (_viewdata != NULL)
//			bgfx::setViewFrameBuffer(it->second, BGFX_INVALID_HANDLE);
//		bgfx::destroy(_viewdata->framebuffer);
//		_viewdata->framebuffer = BGFX_INVALID_HANDLE;
//	}
//}
void bgfx_imgui::reset(int _width, int _height)
{
	ImGuiIO &io = ImGui::GetIO();
	(void)io;
	io.DisplaySize = ImVec2((float)(_width), (float)(_height));

	//bgfx��reset���������е���buffer�������@�Y���؁�
	for (auto it = mapViewID.begin(); it != mapViewID.end(); ++it)
	{
		auto view = ImGui::FindViewportByID(it->first);
		bgfx_OnSetWindowSize(view, view->Size);
	}
}
void bgfx_imgui::Init(void *hwnd, int _width, int _height)
{

	IMGUI_CHECKVERSION();
	ImGui::CreateContext();
	ImGuiIO &io = ImGui::GetIO();
	(void)io;
	io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
	//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;      // Enable Gamepad Controls
	io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;	// Enable Docking
	io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows

	//io.ConfigViewportsNoAutoMerge = true;
	//io.ConfigViewportsNoTaskBarIcon = true;

	// Setup Dear ImGui style
	ImGui::StyleColorsDark();
	//ImGui::StyleColorsClassic();

	// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
	ImGuiStyle &style = ImGui::GetStyle();
	if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
	{
		style.WindowRounding = 0.0f;
		style.Colors[ImGuiCol_WindowBg].w = 1.0f;
	}

	//�m��imgui
	io.DisplaySize = ImVec2((float)(_width), (float)(_height));
	unsigned char *data;
	int width;
	int height;

	//��횳�ʼ��font
	io.Fonts->GetTexDataAsRGBA32(&data, &width, &height);

	m_texture = bgfx::createTexture2D(
		(uint16_t)width, (uint16_t)height, false, 1, bgfx::TextureFormat::BGRA8, 0, bgfx::copy(data, width * height * 4));
	//io.Fonts->ClearTexData();

	//shader init
	bgfx::RendererType::Enum type = bgfx::getRendererType();
	m_program = bgfx::createProgram(
		bgfx::createEmbeddedShader(s_embeddedShaders, type, "vs_imgui_texture"), bgfx::createEmbeddedShader(s_embeddedShaders, type, "fs_ocornut_imgui"), true);

	u_imageLodEnabled = bgfx::createUniform("u_imageLodEnabled", bgfx::UniformType::Vec4);

	m_imageProgram = bgfx::createProgram(
		bgfx::createEmbeddedShader(s_embeddedShaders, type, "vs_imgui_image"), bgfx::createEmbeddedShader(s_embeddedShaders, type, "fs_imgui_image"), true);

	m_layout
		.begin()
		.add(bgfx::Attrib::Position, 2, bgfx::AttribType::Float)
		.add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float)
		.add(bgfx::Attrib::Color0, 4, bgfx::AttribType::Uint8, true)
		.end();

	s_tex = bgfx::createUniform("s_tex", bgfx::UniformType::Sampler);

	io.BackendRendererName = "imgui_impl_bgfx";
	io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
	io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports;

	if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
	{
		ImGuiPlatformIO &platform_io = ImGui::GetPlatformIO();
		platform_io.Renderer_CreateWindow = bgfx_OnCreateSubWindow;
		platform_io.Renderer_DestroyWindow = bgfx_OnDestorySubWindow;
		platform_io.Renderer_SetWindowSize = bgfx_OnSetWindowSize;
		platform_io.Renderer_RenderWindow = bgfx_RenderWindow;
		platform_io.Renderer_SwapBuffers = bgfx_SwapBuffers;
	}
}

int bgfx_imgui::GetBgfxViewID(int imguiID)
{
	auto it = bgfx_imgui::mapViewID.find(imguiID);
	if (it != bgfx_imgui::mapViewID.end())
	{
		return it->second;
	}
	else
	{
		int id = bgfx_imgui::mapViewID.size() + 1;
		bgfx_imgui::mapViewID[imguiID] = id;
		return id;
	}
}

void bgfx_OnCreateSubWindow(ImGuiViewport *viewport)
{
	int viewid = bgfx_imgui::GetBgfxViewID(viewport->ID);
	int w = (int)viewport->Size.x;
	int h = (int)viewport->Size.y;
	void *hwnd = viewport->PlatformHandleRaw;
	auto fth = bgfx::createFrameBuffer(hwnd, w, h, bgfx::TextureFormat::Count, bgfx::TextureFormat::Count);

	viewport->RendererUserData = new viewData{fth, w, h};
	bgfx::setViewFrameBuffer(viewid, fth);
	bgfx::setViewClear(viewid, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xFF00FFFF);
	bgfx::setViewRect(viewid, 0, 0, w, h);
}
void bgfx_OnDestorySubWindow(ImGuiViewport *viewport)
{
	auto _viewData = (viewData *)viewport->RendererUserData;
	if (bgfx::isValid(_viewData->framebuffer))
		bgfx::destroy(_viewData->framebuffer);
	viewport->RendererUserData = NULL;
	delete _viewData;
}
void bgfx_OnSetWindowSize(ImGuiViewport *viewport, ImVec2 size)
{
	int viewid = bgfx_imgui::GetBgfxViewID(viewport->ID);
	void *hwnd = viewport->PlatformHandleRaw;
	int w = (int)size.x;
	int h = (int)size.y;

	auto _viewData = (viewData *)viewport->RendererUserData;

	if (bgfx::isValid(_viewData->framebuffer))
	{
		if (w == _viewData->width && h == _viewData->height)
		{
			//donothing
		}
		else
		{
			bgfx::destroy(_viewData->framebuffer);
			_viewData->framebuffer = BGFX_INVALID_HANDLE;
		}
	}
	if (!bgfx::isValid(_viewData->framebuffer))
	{
		_viewData->framebuffer = bgfx::createFrameBuffer(hwnd, w, h, bgfx::TextureFormat::Count, bgfx::TextureFormat::Count);
	}

	bgfx::setViewFrameBuffer(viewid, _viewData->framebuffer);
	bgfx::setViewClear(viewid, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0xFF00FFFF);
	bgfx::setViewRect(viewid, 0, 0, w, h);
}
void bgfx_RenderWindow(ImGuiViewport *viewport, void *arg)
{
	int viewid = bgfx_imgui::GetBgfxViewID(viewport->ID);
	auto _viewData = (viewData *)viewport->RendererUserData;
	bgfx::setViewFrameBuffer(viewid, _viewData->framebuffer);
	bgfx::touch(viewid);
	bgfx_imgui::Render(viewid, viewport->DrawData);
}
void bgfx_SwapBuffers(ImGuiViewport *viewport, void *arg)
{
}
inline bool checkAvailTransientBuffers(uint32_t _numVertices, const bgfx::VertexLayout &_layout, uint32_t _numIndices)
{
	return _numVertices == bgfx::getAvailTransientVertexBuffer(_numVertices, _layout) && (0 == _numIndices || _numIndices == bgfx::getAvailTransientIndexBuffer(_numIndices));
}

void bgfx_imgui::Render(int nViewId, ImDrawData *_drawData)
{
	// Avoid rendering when minimized
	if (_drawData->DisplaySize.x <= 0.0f || _drawData->DisplaySize.y <= 0.0f)
		return;

	const float width = _drawData->DisplaySize.x;
	const float height = _drawData->DisplaySize.y;

	bgfx::setViewName(nViewId, "ImGui");
	bgfx::setViewMode(nViewId, bgfx::ViewMode::Sequential);

	const bgfx::Caps *caps = bgfx::getCaps();
	{
		//�@��0.5 ��ģ��
		float L = _drawData->DisplayPos.x;							  // +0.5f;
		float R = _drawData->DisplayPos.x + _drawData->DisplaySize.x; // +0.5f;
		float T = _drawData->DisplayPos.y;							  // +0.5f;
		float B = _drawData->DisplayPos.y + _drawData->DisplaySize.y; // +0.5f;
		//D3DMATRIX mat_identity = { { { 1.0f, 0.0f, 0.0f, 0.0f,  0.0f, 1.0f, 0.0f, 0.0f,  0.0f, 0.0f, 1.0f, 0.0f,  0.0f, 0.0f, 0.0f, 1.0f } } };
		//float mat_projection[16] =
		//{
		//   2.0f / (R - L),   0.0f,         0.0f,  0.0f,
		//   0.0f,         2.0f / (T - B),   0.0f,  0.0f,
		//   0.0f,         0.0f,         0.5f,  0.0f,
		//   (L + R) / (L - R),  (T + B) / (B - T),  0.5f,  1.0f
		//};

		float view[16];
		bx::mtxIdentity(view);
		float ortho[16];
		bx::mtxOrtho(ortho, L, R, B, T, 0.0f, 1000.0f, 0.0f, caps->homogeneousDepth);
		bgfx::setViewTransform(nViewId, view, ortho);
		bgfx::setViewRect(nViewId, 0, 0, uint16_t(width), uint16_t(height));
	}

	// Render command lists
	for (int32_t ii = 0, num = _drawData->CmdListsCount; ii < num; ++ii)
	{
		bgfx::TransientVertexBuffer tvb;
		bgfx::TransientIndexBuffer tib;

		const ImDrawList *drawList = _drawData->CmdLists[ii];
		uint32_t numVertices = (uint32_t)drawList->VtxBuffer.size();
		uint32_t numIndices = (uint32_t)drawList->IdxBuffer.size();

		if (!checkAvailTransientBuffers(numVertices, m_layout, numIndices))
		{
			// not enough space in transient buffer just quit drawing the rest...
			break;
		}

		bgfx::allocTransientVertexBuffer(&tvb, numVertices, m_layout);
		bgfx::allocTransientIndexBuffer(&tib, numIndices);

		ImDrawVert *verts = (ImDrawVert *)tvb.data;
		bx::memCopy(verts, drawList->VtxBuffer.begin(), numVertices * sizeof(ImDrawVert));

		ImDrawIdx *indices = (ImDrawIdx *)tib.data;
		bx::memCopy(indices, drawList->IdxBuffer.begin(), numIndices * sizeof(ImDrawIdx));

		uint32_t offset = 0;
		for (const ImDrawCmd *cmd = drawList->CmdBuffer.begin(), *cmdEnd = drawList->CmdBuffer.end(); cmd != cmdEnd; ++cmd)
		{
			if (cmd->UserCallback)
			{
				cmd->UserCallback(drawList, cmd);
			}
			else if (0 != cmd->ElemCount)
			{
				uint64_t state = 0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_MSAA;

				bgfx::TextureHandle th = m_texture;
				bgfx::ProgramHandle program = m_program;

				if (NULL != cmd->TextureId)
				{
					union {
						ImTextureID ptr;
						struct
						{
							bgfx::TextureHandle handle;
							uint8_t flags;
							uint8_t mip;
						} s;
					} texture = {cmd->TextureId};
					state |= 0 != (IMGUI_FLAGS_ALPHA_BLEND & texture.s.flags)
								 ? BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, BGFX_STATE_BLEND_INV_SRC_ALPHA)
								 : BGFX_STATE_NONE;
					th = texture.s.handle;
					if (0 != texture.s.mip)
					{
						const float lodEnabled[4] = {float(texture.s.mip), 1.0f, 0.0f, 0.0f};
						bgfx::setUniform(u_imageLodEnabled, lodEnabled);
						program = m_imageProgram;
					}
				}
				else
				{
					state |= BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, BGFX_STATE_BLEND_INV_SRC_ALPHA);
				}

				const uint16_t xx = uint16_t(bx::max(cmd->ClipRect.x, 0.0f)) - _drawData->DisplayPos.x;
				const uint16_t yy = uint16_t(bx::max(cmd->ClipRect.y, 0.0f)) - _drawData->DisplayPos.y;

				bgfx::setScissor(xx, yy, uint16_t(bx::min(cmd->ClipRect.z, 65535.0f) - xx), uint16_t(bx::min(cmd->ClipRect.w, 65535.0f) - yy));

				bgfx::setState(state);
				bgfx::setTexture(0, s_tex, th);
				bgfx::setVertexBuffer(0, &tvb, 0, numVertices);
				bgfx::setIndexBuffer(&tib, offset, cmd->ElemCount);
				bgfx::submit(nViewId, program);
			}

			offset += cmd->ElemCount;
		}
	}
}
